package com.maikang.exammall.service;

import com.maikang.exammall.api.HospitalController;
import com.maikang.exammall.entity.Package;
import com.maikang.exammall.entity.*;
import com.maikang.exammall.entity.dto.HospitalOrPkgDTO;
import com.maikang.exammall.entity.dto.PackageDetailDTO;
import com.maikang.exammall.entity.dto.SharePackageDTO;
import com.maikang.exammall.exception.BadRequestException;
import com.maikang.exammall.exception.ForbiddenException;
import com.maikang.exammall.repository.HospitalRepository;
import com.maikang.exammall.repository.PackageRepository;
import com.maikang.exammall.repository.SaleRepository;
import com.maikang.exammall.util.ImageUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

import static com.maikang.exammall.entity.constant.ApiCacheKeyConstant.packageApiKey;


@Log4j2
@Service
@RequiredArgsConstructor
public class PackageService extends BaseService<Package, Long, PackageRepository> {
    private static final String sharePackageImageFolder = "share_pkg/";
    private final HospitalRepository hospitalRepository;
    private final SaleRepository saleRepository;
    private final ImageUtil imageUtil;
    private final HospitalService hospitalService;
    private final HospitalController hospitalController;

    @Transactional(readOnly = true)
    public PackageDetailDTO findDetailById(Long id) {
        Optional<Package> byId = super.findById(id);
        if (!byId.isPresent()) {
            return null;
        }
        Package pkg = byId.get();
        PackageDetailDTO detail = PackageDetailDTO.builder().pkg(pkg).build();
        List<PackageAttribute> packageAttributes = pkg.getPackageAttributes();
        if (!ObjectUtils.isEmpty(packageAttributes)) {
            List<PackageAttribute> pkgItems = null;
            List<PackageAttribute> pkgNotices = null;
            List<PackageAttribute> pkgProcedures = null;
            for (PackageAttribute attribute : packageAttributes) {
                switch (attribute.getAttrType()) {
                    case 1:
                        if (pkgItems == null) {
                            pkgItems = new ArrayList<>();
                        }
                        pkgItems.add(attribute);
                        break;
                    case 2:
                        if (pkgNotices == null) {
                            pkgNotices = new ArrayList<>();
                        }
                        pkgNotices.add(attribute);
                        break;
                    case 3:
                        if (pkgProcedures == null) {
                            pkgProcedures = new ArrayList<>();
                        }
                        pkgProcedures.add(attribute);
                        break;
                    default:
                        log.warn("got unexpected attributeType");
                        break;
                }
            }
            if (pkgItems != null) {
                pkgItems.sort(Comparator.comparingInt(PackageAttribute::getOrderNo));
                detail.setPkgItems(pkgItems);
            }
            if (pkgNotices != null) {
                pkgNotices.sort(Comparator.comparingInt(PackageAttribute::getOrderNo));
                detail.setPkgNotices(pkgNotices);
            }
            if (pkgProcedures != null) {
                pkgProcedures.sort(Comparator.comparingInt(PackageAttribute::getOrderNo));
                detail.setPkgProcedures(pkgProcedures);
            }
        }
        return detail;
    }

    @Transactional(readOnly = true)
    public Iterable<Package> findAll() {
        return repository.findAll();
    }


    @Transactional(readOnly = true)
    public Slice<Package> findAll(Package pkg, Pageable pageable) {

        Specification<Package> specification = (Specification<Package>) (root, query, cb) -> {
            List<Predicate> predicatesList = new ArrayList<>();

            if (pkg == null) {
                return cb.and();
            }

            if (pkg.getHospital() != null) {
                Hospital hospital = pkg.getHospital();
                Join<Package, Hospital> joinHospital = root.join("hospital", JoinType.INNER);
                if (hospital.getId() != null) {
                    predicatesList.add(cb.equal(joinHospital.get("id"), hospital.getId()));
                }
                if (hospital.getLevel() != null) {
                    predicatesList.add(cb.equal(joinHospital.get("level"), hospital.getLevel()));
                }
                if (hospital.getCityId() != null) {
                    predicatesList.add(cb.equal(joinHospital.get("cityId"), hospital.getCityId()));
                }
            }

            if (pkg.getPriceIntervalDTO() != null) {
                if (pkg.getPriceIntervalDTO().getLeftPrice() != null) {
                    predicatesList.add(cb.ge(root.get("priceReal"), pkg.getPriceIntervalDTO().getLeftPrice()));
                }
                if (pkg.getPriceIntervalDTO().getRightPrice() != null) {
                    predicatesList.add(cb.le(root.get("priceReal"), pkg.getPriceIntervalDTO().getRightPrice()));
                }
            }

            if (StringUtils.hasLength(pkg.getName())) {
                Predicate name = cb.like(root.get("name"), '%' + pkg.getName() + '%');
                predicatesList.add(name);
            }

            if (pkg.getTarget() != null) {
                if (pkg.getTarget() == 4) { // 4 代表女, 即2， 和3
                    predicatesList.add(cb.greaterThanOrEqualTo(root.get("target"), 2));
                } else {
                    predicatesList.add(cb.equal(root.get("target"), pkg.getTarget()));

                }
            }


            if (!ObjectUtils.isEmpty(pkg.getCategoryIds()) || !ObjectUtils.isEmpty(pkg.getDiseaseIds())) {
                query.groupBy(root.get("id"));
            }

            if (!ObjectUtils.isEmpty(pkg.getCategoryIds())) {
                Join<Package, PackageCategoryRelation> packageCategoryRelations = root.join("packageCategoryRelations", JoinType.INNER);
                CriteriaBuilder.In<Long> inCategory = cb.in(packageCategoryRelations.<Category>get("category").<Long>get("id"));
                pkg.getCategoryIds().forEach(inCategory::value);
                predicatesList.add(inCategory);
            }

            if (!ObjectUtils.isEmpty(pkg.getDiseaseIds())) {
                Join<Package, PackageDiseaseRelation> packageDiseaseRelations = root.join("packageDiseaseRelations", JoinType.INNER);
                CriteriaBuilder.In<Long> inDisease = cb.in(packageDiseaseRelations.<Disease>get("disease").<Long>get("id"));
                pkg.getDiseaseIds().forEach(inDisease::value);
                predicatesList.add(inDisease);
            }

            Predicate[] predicates = new Predicate[predicatesList.size()];
            return cb.and(predicatesList.toArray(predicates));
        };

        return this.findAllSliced(specification, pageable);
    }

    public HospitalOrPkgDTO findByName(String name, Long cityId) {
        Pageable topTwenty = PageRequest.of(0, 20);

        List<Hospital> hospitals;
        if (cityId == null) {
            hospitals = hospitalRepository.findByNameContaining(name, topTwenty);
        } else {
            hospitals = hospitalRepository.findByNameContainingAndCityIdEquals(name, cityId, topTwenty);
        }

        if (!ObjectUtils.isEmpty(hospitals)) {
            hospitalService.addLowestRealPrice(hospitals);
            hospitalController.calculateDistances(hospitals);
            return new HospitalOrPkgDTO<Hospital>(true, hospitals);
        }
        List<Package> pkgs;
        if (cityId == null) {
            pkgs = repository.findByNameContaining(name, topTwenty);
        } else {
            pkgs = repository.findByNameContainingAndHospitalCityId(name, cityId, topTwenty);
        }
        if (!ObjectUtils.isEmpty(pkgs)) {
            return new HospitalOrPkgDTO<>(false, pkgs);
        }
        return null;
    }

    public SharePackageDTO sharePackage(Long pkgId, Long userId) {
        Sale sale = saleRepository.findBySaleId(userId);
        if (sale == null || sale.getRole() == 0) {
            throw new ForbiddenException();
        }
        Package pkg = repository.findById(pkgId).orElseThrow(() -> new BadRequestException("该套餐不存在"));
        String sharePackagePic = sharePackagePic(pkgId, userId, pkg);
        Double earning = pkg.getPriceReal() * pkg.getDistributableRate();
        if (sale.getRole() == 1) {
            earning *= pkg.getFirstSaleRate();
        } else if (sale.getRole() == 2) {
            earning *= pkg.getSecondSaleRate();
        }
        return SharePackageDTO.builder().prise(earning.longValue())
                .shareDocument(pkg.getShareDocument())
                .sharePkgImgUrl(sharePackagePic).build();
    }

    @Cacheable(value = packageApiKey, key = "'sharePkgPic:pkgId:'+#pkgId+'userId:'+#userId")
    public String sharePackagePic(Long pkgId, Long userId, Package pkg) {
        String pkgDetailUrl = "https://www.mkhealth.club/#/packageDetail?id=" + pkgId + "&refererId=" + userId;
        String path = sharePackageImageFolder + "share_pic_" + userId + "_" + pkgId + ".jpg";
        return imageUtil.createSharePackageImage(pkg.getShareImgUrl(), pkg.getHospital().getName(), pkg.getHospital().getAddress(), pkgDetailUrl, path);
    }
}
